Rust & Raspberry Pi で温度センサーの値を AWS IoT Core に Pub してみた
はじめに
テントの中から失礼します、CX事業本部のてんとタカハシです!
タイトルを見てロマンを感じた方、是非お友達になりましょう。Rust、ラズパイ、IoT、うん、かっけー!な気分で記事を書いています。よろしくお願いします。
前々から、Python & Raspberry Pi でセンサー等をいじってお遊びすることはあったのですが、近年 Rust が色々と話題 & 組み込み関連とも相性が良いのでは?といったことから、ずーっとやってみたかったこと、ついにやってみました。
今回は、Rust を使って Raspberry Pi に接続している温度センサーから値を読み取り、AWS IoT Core に Publish する方法についてご紹介したいと思います。
尚、この記事の中では、AWS IoT Core らへんのデプロイ等については語りませんが、下記のリポジトリの方で、Publish した値が AWS IoT Core を経由して DynamoDB に格納されるまでの流れを試して頂くことができるので、お時間あれば是非チェックしてみてください。
GitHub - iam326/send-temp-to-aws-iot-with-rpi
環境
Raspberry Pi OS や Rust のバージョンは下記の通りです。Rust の インストールについては手順を載せますが、Raspberry Pi のセットアップは完了していることを前提とさせてください。
$ lsb_release -a No LSB modules are available. Distributor ID: Raspbian Description: Raspbian GNU/Linux 10 (buster) Release: 10 Codename: buster $ cargo --version cargo 1.44.1 (88ba85757 2020-06-11) $ rustc --version rustc 1.44.1 (c7087fe00 2020-06-17)
また、Raspberry Pi 自体のモデルは、「Raspberry Pi 2 Model B」を使用して記事を書いていますが、今回使用する Pin の配置は、Raspberry Pi 3 or 4 系 と同じになります。
使用する部品
温度センサ ADT7410 と I2C で通信可能な基板を使用します(はんだ付けが必要です)。みんな大好き秋月電子さんで購入可能です。
ADT7410使用 高精度・高分解能 I2C・16Bit 温度センサモジュール
配線
下記の通りに Raspberry Pi と温度センサーを接続してください。
温度センサーとの接続を確認する
Raspberry Pi で I2C を有効にする
raspi-config
から I2C を有効にしてください。ここでは詳細の手順についての説明は省きますが、下記の記事が参考になると思います。
I2C を有効にしたら、Raspberry Pi を再起動する必要があります。
接続を確認する
i2cdetect
を使用して、温度センサーとの接続を確認します。
下記のように、48
と数値が表示されていれば OK です。
$ sudo apt-get install -y i2c-tools $ sudo i2cdetect -y 1 0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --
Raspberry Pi に Rust をインストールする
下記の手順で Raspberry Pi に Rust をインストールしてください。
インストールが完了すると PATH を通すための設定が ~/.profile
に追加されるので、source
を叩いた後、cargo
が叩けるか確認してください。
$ curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh $ tail -n 1 ~/.profile export PATH="$HOME/.cargo/bin:$PATH" $ source ~/.profile $ cargo --version cargo 1.44.1 (88ba85757 2020-06-11)
お待ちかねの実装です
Rust の Project を作成する
cargo
を使用して新しい Project を作成します。Project の名前は何でも良いですが、ここでは「rust-rpi-sample」にします。作成したらそのディレクトリの中に移動してください。
$ cargo new rust-rpi-sample Created binary (application) `rust-rpi-sample` package $ cd rust-rpi-sample/ $ tree . ├── Cargo.toml └── src └── main.rs
使用するクレートの指定
Cargo.toml
内の dependencies
を下記の通りにします。
[dependencies] rppal = "0.11.3" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" rumqtt = "0.31.0" chrono = "0.4.13"
rppal
で Raspberry Pi を操作して、rumqtt
で AWS IoT Core に データを mqtt で Publish します。
プログラム全貌
main.rs
の中身を下記の通りにします。
プログラムを実行する
下記の通りにプログラムを実行すると、10秒おきに温度センサーの値を AWS IoT Core に Publish します(初回実行時は、クレートのインストールにかなりの時間がかかります)。
$ cd src/ $ cargo run main.rs Finished dev [unoptimized + debuginfo] target(s) in 1.03s Running `<PATH>/rust-rpi-sample/target/debug/rust-rpi-sample` Publish(Publish { dup: false, qos: AtLeastOnce, retain: false, topic_name: "iot/topic", pkid: Some(PacketIdentifier(1)), payload: [123, 34, 116, 105, 109, 101, 115, 116, 97, 109, 112, 34, 58, 49, 53, 57, 53, 51, 52, 51, 51, 53, 53, 44, 34, 116, 101, 109, 112, 101, 114, 97, 116, 117, 114, 101, 34, 58, 50, 55, 46, 48, 125] }) Publish(Publish { dup: false, qos: AtLeastOnce, retain: false, topic_name: "iot/topic", pkid: Some(PacketIdentifier(1)), payload: [123, 34, 116, 105, 109, 101, 115, 116, 97, 109, 112, 34, 58, 49, 53, 57, 53, 51, 52, 51, 51, 54, 53, 44, 34, 116, 101, 109, 112, 101, 114, 97, 116, 117, 114, 101, 34, 58, 50, 55, 46, 48, 125] }) Publish(Publish { dup: false, qos: AtLeastOnce, retain: false, topic_name: "iot/topic", pkid: Some(PacketIdentifier(1)), payload: [123, 34, 116, 105, 109, 101, 115, 116, 97, 109, 112, 34, 58, 49, 53, 57, 53, 51, 52, 51, 51, 55, 53, 44, 34, 116, 101, 109, 112, 101, 114, 97, 116, 117, 114, 101, 34, 58, 50, 55, 46, 48, 125] }) ...
プログラムの細かい説明
温度センサーの値を読み取る部分
プログラムの中で、センサーの値を読み取っている部分は下記になります。
const BUS: u8 = 1; const ADDRESS_ADT7410: u16 = 0x48; const ADDRESS_REGISTER: u8 = 0x00; ... let mut i2c = I2c::with_bus(BUS).expect("Couldn't start i2c. Is the interface enabled?"); i2c.set_slave_address(ADDRESS_ADT7410).unwrap(); ... fn read_temperature(i2c: &I2c) -> f32 { let word = i2c.smbus_read_word(ADDRESS_REGISTER).unwrap(); let data = ((word & 0xff00) >> 8 | (word & 0xff) << 8) >> 3; if data & 0x1000 == 0 { data as f32 * 0.0625 } else { ((!data & 0x1fff) + 1) as f32 * -0.0625 } }
更に分解して説明します。
I2C で 温度センサーの値を取得するための準備をする
I2C のバス & 温度センサーのアドレスを指定して、温度センサーと通信できるようにします。
const BUS: u8 = 1; const ADDRESS_ADT7410: u16 = 0x48; ... let mut i2c = I2c::with_bus(BUS).expect("Couldn't start i2c. Is the interface enabled?"); i2c.set_slave_address(ADDRESS_ADT7410).unwrap();
バスについては rppal - I2c - with_bus の説明から、バス1番を指定すれば良さそうなことが分かります(Raspberry Pi 1 の場合は0番になります)。
On the Raspberry Pi Model B Rev 1, those pins are tied to bus 0. On every other Raspberry Pi model, they're connected to bus 1.
温度センサーのアドレスについては マニュアル の説明から、0x48
であることが分かります。
I2C のバスアドレスは、ADT7410 の 3 番・4 番ピンで設定します。 基板のデフォルト状態で A1-A0:0-0(0x48) です。
温度センサーの値を読み取る
実際に I2C で温度センサーの値を読み取っていきます。
const ADDRESS_REGISTER: u8 = 0x00; ... fn read_temperature(i2c: &I2c) -> f32 { let word = i2c.smbus_read_word(ADDRESS_REGISTER).unwrap(); let data = ((word & 0xff00) >> 8 | (word & 0xff) << 8) >> 3; if data & 0x1000 == 0 { data as f32 * 0.0625 } else { ((!data & 0x1fff) + 1) as f32 * -0.0625 } }
データシート から、レジスタ先頭 2byte が温度の値であることが分かるので、i2c.smbus_read_word(ADDRESS_REGISTER)
で 2byte 読み取っています。
Table 6. ADT7410 Registers
...
0x00 | Temperature value most significant byte
0x01 | Temperature value least significant byte
読み取った値のフォーマットについても、データシート から、先頭 3bit を除いた 13bit が 0.0625°C 刻みで入っていることが分かるので、後段の処理で少しごにょごにょしています。条件分岐の部分は、温度が + なのか - なのかを判定して、その結果に合わせて変換方法を変えています。
Table 5. 13-Bit Temperature Data Format
...
−0.0625°C | 1 1111 1111 1111
0°C | 0 0000 0000 0000
+0.0625°C | 0 0000 0000 0001
AWS IoT Core に Publish する部分
プログラムの中で、AWS IoT Core に Publish する部分は下記になります。
エンドポイントや証明書、秘密鍵等を読み込んで mqtt クライアントを作成した後、トピックと payload を指定して Publish しています。他の言語で AWS IoT Core を触ったことがある方には、すぐ理解できる内容だと思います。
let client_id = env::var("AWS_IOT_CLIENT_ID").expect("AWS_IOT_CLIENT_ID is undefined."); let aws_iot_endpoint = env::var("AWS_IOT_ENDPOINT").expect("AWS_IOT_ENDPOINT is undefined."); let ca_path = "AmazonRootCA1.pem"; let client_cert_path = "certificate.pem.crt"; let client_key_path = "private.pem.key"; let mqtt_options = MqttOptions::new(client_id, aws_iot_endpoint, 8883) .set_ca(read(ca_path).unwrap()) .set_client_auth(read(client_cert_path).unwrap(), read(client_key_path).unwrap()) .set_keep_alive(10) .set_reconnect_opts(ReconnectOptions::Always(5)); let (mut mqtt_client, notifications) = MqttClient::start(mqtt_options).unwrap(); mqtt_client.subscribe("iot/topic", QoS::AtLeastOnce).unwrap(); ... mqtt_client.publish("iot/topic", QoS::AtLeastOnce, false, payload).unwrap();
下記のリポジトリにて、Publish したデータが DynamoDB に格納される流れを確認できます。
GitHub - iam326/send-temp-to-aws-iot-with-rpi
おわりに
今回は、Rust & Raspberry Pi で 温度センサーの値を AWS IoT Core に Publish する方法についてご紹介しましたが、いかがだったでしょうか。
私自身は、ほぼ初めて Rust に触れたということもあり、この記事を書くまでに結構な時間がかかりました。その分、やりたかったことが実現できたときの感動は最大レベルで最&高でした。 まだ Rust について全然詳しくないので、基本文法レベルでちょいちょい勉強していきたいのと、引き続きセンサー周りも動かしていきたいな〜と考えています。
もしこの記事を見て、Raspberry Pi でセンサー等を動かしてみたいと思った方がいれば、ブルーバックス出版の Raspberry Piで学ぶ電子工作 作る、動かす、しくみがわかる! がオススメです。
今回は以上になります。ありがとうございました。